

namespace ODataValidator.ValidationService
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;

    /// <summary>Write the test result into TestResults table</summary>
    public class DatabaseResultProvider : RuleEngine.IResultProvider, RuleEngine.ILogger
    {
        /// <summary>Batch size for writting test result</summary>
        private const int ResultBatchSize = 4;

        /// <summary>Job Id</summary>
        private Guid jobId;

        /// <summary>List of the TestResult</summary>
        private List<TestResult> resultsToSave;

        /// <summary>Constructor</summary>
        /// <param name="validationJobId">Guid</param>
        public DatabaseResultProvider(Guid validationJobId)
        {
            this.jobId = validationJobId;
            this.resultsToSave = new List<TestResult>();
        }

        /// <summary>Save the test results to TestResults table</summary>
        /// <param name="result">TestResult</param>
        public void Accept(RuleEngine.TestResult result)
        {
            if (result == null)
            {
                return;
            }

            TestResult testResult = new TestResult();
            testResult.ValidationJobID = this.jobId;
            testResult.RuleName = result.RuleName;
            testResult.Description = result.Description;

            // TODO: need ErrorMessage property on CheckResult
            testResult.ErrorMessage = "";  
            testResult.HelpUri = result.HelpLink;

            // TODO: need spec back in HTML form.
            testResult.SpecificationUri = result.SpecificationSection; 
            testResult.Classification = result.Classification;
            testResult.ODataLevel = result.Category;
            testResult.LineNumberInError = result.LineNumberInError.ToString(CultureInfo.CurrentCulture);

            this.resultsToSave.Add(testResult);

            // save results to DB in batches of 5
            if (this.resultsToSave.Count >= ResultBatchSize)
            {
                using (var ctx = new ODataValidationSuiteEntities())
                {
                    this.ProcessResultsBatch(ctx);
                }
            }
        }

        /// <summary>Mark the ValidationJob complete</summary>
        /// <param name="errorOccurred">True of false if there's any Rule Engine exception</param>
        public void JobCompleted(bool errorOccurred)
        {
            using (var ctx = new ODataValidationSuiteEntities())
            {
                this.ProcessResultsBatch(ctx);

                var job = (from j in ctx.ExtValidationJobs
                           where j.ID == this.jobId
                           select j).FirstOrDefault();
                if (job != null)
                {
                    job.Complete = true;
                    job.ErrorOccurred = errorOccurred;
                    job.CompleteDate = DateTime.Now;
                    ctx.SaveChanges();
                }
            }
        }

        /// <summary>Save the exception into EngineRuntimeExceptions table</summary>
        /// <param name="runtimeError">Rule Engine exception</param>
        [SuppressMessage("DataWeb.Usage", "AC0014:DoNotHandleProhibitedExceptionsRule", Justification = "Taken care of by similar mechanism")]
        public void Log(RuleEngine.RuntimeException runtimeError)
        {
            if (runtimeError == null)
            {
                return;
            }

            try
            {
                using (var ctx = new ODataValidationSuiteEntities())
                {
                    var runtimeException = EngineRuntimeException.CreateEngineRuntimeException(
                        runtimeError.JobId,
                        runtimeError.RuleName,
                        runtimeError.Timestamp,
                        runtimeError.DestinationEndpoint,
                        0);
                    runtimeException.Message = runtimeError.Message;
                    runtimeException.StackTrace = runtimeError.StackTrace;
                    runtimeException.Detail = runtimeError.Detail;

                    ctx.AddToEngineRuntimeExceptions(runtimeException);
                    ctx.SaveChanges();
                }
            }
            catch (Exception ex)
            {
                if (!RuleEngine.Common.ExceptionHelper.IsCatchableExceptionType(ex))
                {
                    throw;
                }

                // swallows the exception since logging is considered low-prio task
            }
        }

        /// <summary>Save the test results to TestResults table</summary>
        /// <param name="ctx">ODataValidationSuiteEntities</param>
        private void ProcessResultsBatch(ODataValidationSuiteEntities ctx)
        {
            // write back to the DB in blocks of 5 test results
            foreach (TestResult res in this.resultsToSave)
            {
                ctx.AddToTestResults(res);
            }

            ctx.SaveChanges();
            this.resultsToSave.Clear();
        }
    }
}
